((global, factory) => {
  typeof exports === "object" && typeof module !== "undefined"
    ? (module.exports = factory(global))
    : typeof define === "function" && define.amd
    ? define(() => factory(global))
    : ((global = typeof globalThis !== "undefined" ? globalThis : global),
      (global.mfoRequire = factory(global)));
})(window, (global) => {
  const globalDepends = {};
  class Depend {
    constructor(ops) {
      Object.assign(this, ops);
      return this;
    }
    url = undefined;
    depends = [];
    fn = undefined;
    exports = {};
    update(ops) {
      Object.assign(this, ops);
      return this;
    }
  }

  const head = global.document.querySelector("head");
  const baseElement = head.getElementsByTagName("base")[0];

  class Request {
    constructor(ops) {
      this.options = ops || {};
      return this;
    }
    get() {
      const that = this;
      const depend = that.options.depend;

      const elem = global.document.createElement("script");
      elem.async = true;
      if (depend && globalDepends[depend]) {
        elem.src = globalDepends[depend].url;
        globalDepends[depend].loaded = () => {
          that.options.callback({ [depend]: globalDepends[depend].exports });
        };
      } else {
        elem.src = that.options.url;
      }
      const onload = () => {
        // console.log("  [loaded]", depend);
        elem.removeEventListener("load", () => {});
        elem.removeEventListener("readystatechange", () => {});
        !depend && that.options.success();
        if (depend) {
          try {
            elem && head.removeChild(elem);
            elem = null;
          } catch (err) {}
        }
      };
      "onload" in elem
        ? elem.addEventListener("load", () => {
            onload();
          })
        : elem.addEventListener("readystatechange", () => {
            if (/loaded|complete/.test(elem.readyState)) {
              onload();
            }
          });
      elem.addEventListener("error", () => {
        that.options.catch(new Error("Module load error"));
      });

      baseElement
        ? head.insertBefore(elem, baseElement)
        : head.appendChild(elem);
    }
  }
  class RequestControl {
    constructor() {
      return this;
    }
    tit = [];
    load(depends) {
      const that = this;
      return new Promise((resolve, reject) => {
        if (typeof depends === "string") {
          depends = [depends];
        }
        depends.forEach((depend) => {
          that.tit.push({
            factory: (callback) => new Request({ depend, callback }).get(),
            depend,
          });
        });

        if (that.tit.length > 0) {
          const result = {};
          const emit = (n) => {
            that.tit[n].factory((res) => {
              Object.assign(result, res);
              if (n + 1 < that.tit.length) {
                n += 1;
                emit(n);
              } else {
                resolve(result);
              }
            });
          };
          emit(0);
        } else {
          resolve();
        }
      });
    }
  }

  class Require {
    constructor(ops) {
      if (ops) {
        this.options = ops;
        const alias = Object.assign({}, ops.alias);
        Object.keys(alias).forEach((key) => {
          globalDepends[key] = new Depend({
            url: (ops.base || "/") + alias[key],
          });
        });
      }
      return this;
    }
    load(depends) {
      return new RequestControl().load(depends, this.options.debug);
    }
  }

  global.define = function(/* name, depends, fn */) {
    const args = arguments,
      len = args.length;
    let name = undefined,
      depends = undefined,
      factory = undefined;
    switch (len) {
      case 0:
      case 1:
        return;
      case 2:
        Array.isArray(args[0]) ? (depends = args[0]) : (name = args[0]);
        factory = args[1];
        break;
      default:
        name = args[0];
        depends = args[1];
        factory = args[2];
        break;
    }

    console.log(" [define]", name);

    const require = (depend) => {
      const res = {};
      Array.isArray(depend)
        ? depend.forEach((dp) => {
            globalDepends[dp] &&
              Object.assign(res, {
                [dp]: globalDepends[dp].exports || {},
              });
          })
        : (res[depend] = globalDepends[depend]
            ? globalDepends[depend].exports
            : {});
      return res;
    };

    const exports = {};

    const meta = {
      name,
      url: name && globalDepends[name] && globalDepends[name].url,
      depends,
      exports,
      events: [],
      fn: () => {
        new RequestControl().load(depends).then(() => {
          factory.call(meta.exports, require, meta.exports);
          globalDepends[meta.name] && globalDepends[meta.name].loaded();
        });
      },
    };
    if (name && globalDepends[name]) {
      globalDepends[meta.name].update(meta);
    }
    meta.fn();
  };

  return {
    createRequire(ops) {
      return new Require(ops);
    },
    createRequest(ops) {
      return new Request(ops).get();
    },
  };
});
